package net.oschina.allchat.server.framework.pipe.routing;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import net.oschina.allchat.message.processing.ProcessingService;
import net.oschina.allchat.message.processing.TranslatingFactory;
import net.oschina.allchat.message.processing.convention.NamingConventionTranslatorFactory;
import net.oschina.allchat.message.processing.translating.ITranslatingFactory;
import net.oschina.allchat.message.processing.translating.ITranslatorFactory;
import net.oschina.allchat.message.processing.translators.SimpleObjectTranslatorFactory;
import net.oschina.allchat.message.processing.translators.core.stanza.IqTranslatorFactory;
import net.oschina.allchat.message.processing.translators.error.StanzaErrorTranslatorFactory;
import net.oschina.allchat.protocol.core.JabberId;
import net.oschina.allchat.protocol.core.Protocol;
import net.oschina.allchat.protocol.core.ProtocolException;
import net.oschina.allchat.protocol.core.stanza.Iq;
import net.oschina.allchat.protocol.core.stanza.Stanza;
import net.oschina.allchat.protocol.core.stanza.error.InternalServerError;
import net.oschina.allchat.protocol.core.stanza.error.StanzaError;
import net.oschina.allchat.server.framework.core.commons.osgi.IBundleContextAware;
import net.oschina.allchat.server.framework.core.commons.osgi.IContributionTracker;
import net.oschina.allchat.server.framework.core.commons.osgi.OsgiUtils;
import net.oschina.allchat.server.framework.core.commons.utils.CommonUtils;
import net.oschina.allchat.server.framework.core.connection.IConnectionContext;
import net.oschina.allchat.server.framework.core.integration.IMessage;
import net.oschina.allchat.server.framework.core.integration.IMessageProcessor;
import net.oschina.allchat.server.framework.core.integration.SimpleMessage;
import net.oschina.allchat.server.framework.core.repository.IInitializable;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

public class MinimumRoutingProcessor implements IMessageProcessor, IBundleContextAware,
			IContributionTracker, IInitializable {
	private static final String KEY_ALLCHAT_TRANSLATORS = "AllChat-Translators";
	private static final String SEPARATOR_TRANSLATORS = ",";
	private static final String PROPERTY_NAME_TYPE = "type";
	private static final String PROPERTY_NAME_CLASS = "class";
	private static final String PROPERTY_NAME_TRANSLATOR_FACTORY = "translator-factory";
	private static final String PROPERTY_NAME_PROTOCOL = "protocol";
	private static final String TYPE_SIMPLE = "simple";
	private static final String TYPE_NAMING_CONVENTION = "naming-convention";
	private static final String TYPE_CUSTOM = "custom";
	private static final String SEPARATOR_OF_LOCALNAME_NAMESPACE = "|";
	private static final String VALUE_NULL = "null";
	
	protected ITranslatingFactory translatingFactory;
	protected BundleContext bundleContext;
	
	protected Map<Bundle, List<Class<?>>> bundleAndClasses;
	
	public MinimumRoutingProcessor() {
		translatingFactory = new TranslatingFactory(ProcessingService.createMessageWriterFactory());
		bundleAndClasses = new HashMap<Bundle, List<Class<?>>>();
	}
	
	@Override
	public void init() {
		registerPredefinedTranslators();
		trackContributedTranslators();
	}
	
	protected void registerPredefinedTranslators() {
		translatingFactory.register(Iq.class, new IqTranslatorFactory());
		translatingFactory.register(StanzaError.class, new StanzaErrorTranslatorFactory());
	}

	protected void trackContributedTranslators() {
		OsgiUtils.trackContribution(bundleContext, KEY_ALLCHAT_TRANSLATORS, this);
	}

	@Override
	public void process(IConnectionContext context, IMessage message) {
		Object object = message.getPayload();
		
		JabberId sessionId = (JabberId)message.getHeader().get(IMessage.KEY_SESSION_ID);
		String msgString = null;
		JabberId target = null;
		try {
			msgString = translatingFactory.translate(object);
			
			if (object instanceof Stanza) {
				target = ((Stanza)object).getTo();
			}
			
			if (target == null) {
				target = sessionId;
			}
		} catch (ProtocolException e) {
			try {
				msgString = translatingFactory.translate(e.getError());
			} catch (Exception e2) {
				msgString = translatingFactory.translate(new InternalServerError(e.getMessage()));
			}
			
		} catch (RuntimeException e) {
			msgString = translatingFactory.translate(new InternalServerError(e.getMessage()));
			
		}
		
		Map<Object, Object> header = new HashMap<Object, Object>();
		header.put(IMessage.KEY_SESSION_ID, sessionId);
		header.put(IMessage.KEY_MESSAGE_TARGET, target);
		
		context.write(new SimpleMessage(header, msgString));
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public void found(Bundle bundle, String contribution) throws Exception {
		StringTokenizer tokenizer = new StringTokenizer(contribution, SEPARATOR_TRANSLATORS);
		
		List<Class<?>> classes = new ArrayList<Class<?>>();
		while (tokenizer.hasMoreTokens()) {
			String parserString = tokenizer.nextToken();
			
			Map<String, String> properties = CommonUtils.parsePropertiesString(parserString,
					new String[] {
						PROPERTY_NAME_CLASS,
						PROPERTY_NAME_TYPE,
						PROPERTY_NAME_TRANSLATOR_FACTORY,
						PROPERTY_NAME_PROTOCOL
					}
			);
			
			String sClass = properties.get(PROPERTY_NAME_CLASS);
			if (sClass == null)
				throw new IllegalArgumentException("null class[register translator]");
			
			Class<?> clazz = bundle.loadClass(sClass);
			
			String sType = properties.get(PROPERTY_NAME_TYPE);
			if (sType == null) {
				sType = TYPE_CUSTOM;
			}
			
			ITranslatorFactory<?> translatorFactory;
			if (TYPE_CUSTOM.equals(sType)) {
				String sTranslatorFactory = properties.get(PROPERTY_NAME_TRANSLATOR_FACTORY);
				if (sTranslatorFactory == null)
					throw new IllegalArgumentException("null translator factory[register translator]");
				
				Class<?> parserFactoryClass = bundle.loadClass(sTranslatorFactory);
				
				if (!(ITranslatorFactory.class.isAssignableFrom(parserFactoryClass)))
					throw new RuntimeException(String.format("%s must implement %s[register translator]",
							parserFactoryClass, ITranslatorFactory.class));
					
				translatorFactory = (ITranslatorFactory<?>)parserFactoryClass.newInstance();
			} else {
				String sProtocol = properties.get(PROPERTY_NAME_PROTOCOL);
				if (sProtocol == null)
					throw new IllegalArgumentException("null protocol[register translator]");
				
				Protocol protocol = parseProtocol(sProtocol);
				
				if (TYPE_SIMPLE.equals(sType)) {
					translatorFactory = new SimpleObjectTranslatorFactory(clazz, protocol);
				} else if (TYPE_NAMING_CONVENTION.equals(sType)) {
					translatorFactory = new NamingConventionTranslatorFactory(protocol, clazz);
				} else {
					throw new RuntimeException(String.format("unknown translator type %s", sType));
				}
			}
			
			translatingFactory.register(clazz, translatorFactory);
			classes.add(clazz);
		}
		
		bundleAndClasses.put(bundle, classes);
	}

	private Protocol parseProtocol(String sProtocol) {
		String localName;
		String namespace;
		
		int seperator = sProtocol.indexOf(SEPARATOR_OF_LOCALNAME_NAMESPACE);
		if (seperator == -1) {
			localName = sProtocol.trim();
			namespace = VALUE_NULL;
		} else {
			localName = sProtocol.substring(0, seperator).trim();
			namespace = sProtocol.substring(seperator + 1, sProtocol.length()).trim();
		}
		
		if (localName.length() == 0 || namespace.length() == 0) {
			throw new IllegalArgumentException(String.format("invalid protocol", sProtocol));
		}
		
		if (VALUE_NULL.equals(namespace)) {
			namespace = null;
		}
		
		return new Protocol(namespace, localName);
	}

	@Override
	public void lost(Bundle bundle, String contribution) throws Exception {
		List<Class<?>> classes = bundleAndClasses.remove(bundle);
		
		if (classes != null) {
			for (Class<?> clazz : classes) {
				translatingFactory.unregister(clazz);
			}
		}
	}

	@Override
	public void setBundleContext(BundleContext bundleContext) {
		this.bundleContext = bundleContext;
	}

}
